<!DOCTYPE html>
<pre>
<script>

    const COLOR_MAP = {
        'Comments' : '#394931',
        'NumberLiteral': '#D16969',
        'BooleanLiteral': '#569CD6',
        'StringLiteral': '#CE9178',
        'NullLiteral': '#568695',
        'Identifier': '#4FC1DB',
        'Keywords': '#3CC6B0',
        'Punctuator': '#84987D'
    }

    class XRegExp {
        constructor(source, flag, root = "root") {
            this.table = new Map();
            this.regexp = new RegExp(this.compileRegExp(source, root, 0).source, flag);
            console.log(this.regexp);
            console.log(this.table);
        }

        compileRegExp(source, name, start) {
            if (source[name] instanceof RegExp) {
                return {
                    source: source[name].source,
                    length: 0
                };
            }

            let length = 0;

            let regexp = source[name].replace(/\<([^>]+)\>/g, (str, $1) => {
                this.table.set(start + length, $1);
                // this.table.set($1, start + length);
                ++length;

                let r = this.compileRegExp(source, $1, start + length);
                length += r.length;

                return "(" + r.source + ")";
            });

            return {
                source: regexp,
                length: length
            };
        }

        exec(string) {
            let r = this.regexp.exec(string);
            for (let i = 1; i < r.length; i++) {
                if (r[i] !== void 0) {
                    r[this.table.get(i - 1)] = r[i];
                    console.log(this.table.get(i - 1), "--->", r[i]);
                    r.color = COLOR_MAP[this.table.get(i - 1)] || '';
                }
            }
            // console.log(JSON.stringify(r[0]));
            return r;
        }

        get lastIndex() {
            return this.regexp.lastIndex;
        }

        set lastIndex(value) {
            return this.regexp.lastIndex = value;
        }
    }



    // function compileRegExp(xregexp, name) {
      
    //     if (xregexp[name] instanceof RegExp) {
    //         return xregexp[name].source;
    //     }
    //     console.log(xregexp[name]);
    //     let regexp = xregexp[name].replace(/\<([^>]+)\>/g, function (str, $1) {
    //         return compileRegExp(xregexp, $1);
    //     });

    //     return regexp;
    // }

    function scan(str) {
        let regexp = new XRegExp({
            InputElement: "<Whitespace>|<LineTerminator>|<Comments>|<Token>",
            Whitespace: / /,
            LineTerminator: /\n/,
            Comments: /\/\*(?:[^*]|\*[^\/])*\*\/|\/\/[^\n]*/,
            Token: "<Literal>|<Keywords>|<Identifier>|<Punctuator>",
            Literal: "<NumberLiteral>|<BooleanLiteral>|<StringLiteral>|<NullLiteral>",
            NumberLiteral: /(?:[1-9][0-9]*|0)(?:\.[0-9]*)?|\.[0-9]+/,
            BooleanLiteral: /true|false/,
            StringLiteral: /\"(?:[^"\n]|\\[\s\S])*\"|\'(?:[^'\n]|\\[\s\S])*\'/,
            NullLiteral: /null/,
            Identifier: /[a-zA-Z_$][a-zA-Z0-9_$]*/,
            Keywords: /if|else|for|function/,
            Punctuator: /\+|\,|\?|\:|\*|\/|\{|\}|\.|\(|\=|\<|\+\+|\=\=|\=\>|\*|\)|\[|\]|;/,
        }, "g", "InputElement");
        while (regexp.lastIndex < str.length) {
            let r = regexp.exec(str);
            console.log(r);
            document.write(`<font color="${r.color}">${r[0]}</font>`);

            if (!r[0].length) 
                break;
        }
    }

    scan(`
    for(let i = 0; i < 3;i++) {
        for(let j = 0; j < 3; j++) {
            let cell = document.createElement("div");
            cell.classList.add("cell");
            cell.innerText = pattern[i * 3 + j] == 2 ? "✖" :
                pattern[i * 3 + j] == 1 ? "◎" : "";
            cell.addEventListener("click", () => userMove(j, i));
            board.appendChild(cell);
        }
        board.appendChild(document.createElement("br"));
    }
`)
</script>
</pre>